1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

pkgconfig: improve and fix the parser; move to v.pkgconfig (#6695)

This commit is contained in:
pancake
2020-10-29 10:57:23 +01:00
committed by GitHub
parent 423044d4d6
commit 367067dfff
6 changed files with 31 additions and 24 deletions

View File

@@ -0,0 +1,66 @@
v-pkgconfig
===========
This module implements the `pkg-config` tool as a library in pure V.
Features:
* Simple API, but still not stable, but shouldnt change much
* Runs 2x faster than original pkg-config
* Commandline tool that aims to be compatible with `pkg-config`
* Resolve full path for `.pc` file given a name
* Recursively parse all the dependencies
* Find and replace all inner variables
Todo/Future/Wish:
* 100% compatibility with `pkg-config` options
* Integration with V, to support pkgconfig with `system()`
* Strictier pc parsing logic, with better error reporting
Example
-------
The commandline tool is available in `vlib/pkgconfig/bin/pkgconfig.v`
```
$ ./bin/pkgconfig -h
pkgconfig 0.2.0
-----------------------------------------------
Usage: pkgconfig [options] [ARGS]
Options:
-V, --modversion show version of module
-d, --description show pkg module description
-h, --help show this help message
-D, --debug show debug information
-l, --list-all list all pkgmodules
-e, --exists return 0 if pkg exists
-V, --print-variables display variable names
-r, --print-requires display requires of the module
-a, --atleast-version <string>
return 0 if pkg version is at least the given one
--exact-version <string>
return 0 if pkg version is at least the given one
-v, --version show version of this tool
-c, --cflags output all pre-processor and compiler flags
-I, --cflags-only-I show only -I flags from CFLAGS
--cflags-only-other show cflags without -I
-s, --static show --libs for static linking
-l, --libs output all linker flags
--libs-only-l show only -l from ldflags
-L, --libs-only-L show only -L from ldflags
--libs-only-other show flags not containing -l or -L
$
```
Using the API from the V repl
```
>>> import pkgconfig
>>> opt := pkgconfig.Options{}
>>> mut pc := pkgconfig.load('r_core', opt) or { panic(err) }
>>> pc.libs
['-L/usr/local/lib', '-lr_core', '-lr_config', '-lr_util', '', '-ldl', '-lr_cons', '-lr_io', '-lr_socket', '-lr_hash', '-lr_crypto', '-lr_flag', '-lr_asm', '-lr_syscall', '-lr_lang', '-lr_parse', '-lr_reg', '-lr_debug', '-lr_anal', '-lr_search', '-lr_bp', '-lr_egg', '-lr_bin', '-lr_magic', '-lr_fs']
>>>
```

View File

@@ -0,0 +1,18 @@
module main
import v.pkgconfig
import os
fn main() {
mut m := pkgconfig.main(os.args[1..]) or {
eprintln(err)
exit(1)
}
m.res = m.run() or {
eprintln(err)
exit(1)
}
if m.res != '' {
println(m.res)
}
}

209
vlib/v/pkgconfig/main.v Normal file
View File

@@ -0,0 +1,209 @@
module pkgconfig
import flag
import strings
pub struct Main {
pub mut:
opt &MainOptions
res string
has_actions bool
}
struct MainOptions {
modversion bool
description bool
help bool
debug bool
listall bool
exists bool
variables bool
requires bool
atleast string
atleastpc string
exactversion string
version bool
cflags bool
cflags_only_path bool
cflags_only_other bool
stat1c bool
libs bool
libs_only_link bool
libs_only_path bool
libs_only_other bool
args []string
}
fn desc(mod string) ?string {
options := Options{
norecurse: true
}
mut pc := load(mod, options) or {
return error('cannot parse')
}
return pc.description
}
pub fn main(args []string) ?&Main {
mut fp := flag.new_flag_parser(args)
fp.application('pkgconfig')
fp.version(version)
mut m := &Main{
opt: parse_options(mut fp)
}
opt := m.opt
if opt.help {
m.res = fp.usage().replace('- ,', ' ')
} else if opt.version {
m.res = version
} else if opt.listall {
mut modules := list()
modules.sort()
if opt.description {
for mod in modules {
d := desc(mod) or {
continue
}
pad := strings.repeat(` `, 20 - mod.len)
m.res += '$mod $pad $d\n'
}
} else {
m.res = modules.join('\n')
}
} else if opt.args.len == 0 {
return error('No packages given')
}
return m
}
pub fn (mut m Main) run() ?string {
options := Options{
debug: m.opt.debug
}
// m.opt = options
opt := m.opt
mut pc := &PkgConfig(0)
mut res := m.res
for arg in opt.args {
mut pcdep := load(arg, options) or {
if !opt.exists {
return error(err)
}
continue
}
if opt.description {
if res != '' {
res += '\n'
}
res += pcdep.description
}
if pc != 0 {
pc.extend(pcdep)
} else {
pc = pcdep
}
}
if opt.exists {
return res
}
if opt.exactversion != '' {
if pc.version != opt.exactversion {
return error('version mismatch')
}
return res
}
if opt.atleast != '' {
if pc.atleast(opt.atleast) {
return error('version mismatch')
}
return res
}
if opt.atleastpc != '' {
if atleast(opt.atleastpc) {
return error('version mismatch')
}
return res
}
if opt.variables {
for k, _ in pc.vars {
res += '$k\n'
}
}
if opt.requires {
res += pc.requires.join('\n')
}
mut r := []string{}
if opt.cflags_only_path {
r << filter(pc.cflags, '-I', '')
}
if opt.cflags_only_other {
r << filter(pc.cflags, '-I', '-I')
}
if opt.cflags {
r << pc.cflags.join(' ')
}
if opt.libs_only_link {
r << filter(pc.libs, '-l', '')
}
if opt.libs_only_path {
r << filter(pc.libs, '-L', '')
}
if opt.libs_only_other {
r << filter(pc.libs, '-l', '-L')
}
if opt.libs {
if opt.stat1c {
r << pc.libs_private.join(' ')
} else {
r << pc.libs.join(' ')
}
}
if opt.modversion {
r << pc.version
}
return res + r.join(' ')
}
fn filter(libs []string, prefix string, prefix2 string) string {
mut res := ''
if prefix2 != '' {
for lib in libs {
if !lib.starts_with(prefix) && !lib.starts_with(prefix2) {
res += ' $lib'
}
}
} else {
for lib in libs {
if lib.starts_with(prefix) {
res += ' $lib'
}
}
}
return res
}
fn parse_options(mut fp flag.FlagParser) &MainOptions {
return &MainOptions{
description: fp.bool('description', `d`, false, 'show pkg module description')
modversion: fp.bool('modversion', `V`, false, 'show version of module')
help: fp.bool('help', `h`, false, 'show this help message')
debug: fp.bool('debug', `D`, false, 'show debug information')
listall: fp.bool('list-all', `l`, false, 'list all pkgmodules')
exists: fp.bool('exists', `e`, false, 'return 0 if pkg exists')
variables: fp.bool('print-variables', `V`, false, 'display variable names')
requires: fp.bool('print-requires', `r`, false, 'display requires of the module')
atleast: fp.string('atleast-version', `a`, '', 'return 0 if pkg version is at least the given one')
atleastpc: fp.string('atleast-pkgconfig-version', `A`, '', 'return 0 if pkgconfig version is at least the given one')
exactversion: fp.string('exact-version', ` `, '', 'return 0 if pkg version is at least the given one')
version: fp.bool('version', `v`, false, 'show version of this tool')
cflags: fp.bool('cflags', `c`, false, 'output all pre-processor and compiler flags')
cflags_only_path: fp.bool('cflags-only-I', `I`, false, 'show only -I flags from CFLAGS')
cflags_only_other: fp.bool('cflags-only-other', ` `, false, 'show cflags without -I')
stat1c: fp.bool('static', `s`, false, 'show --libs for static linking')
libs: fp.bool('libs', `l`, false, 'output all linker flags')
libs_only_link: fp.bool('libs-only-l', ` `, false, 'show only -l from ldflags')
libs_only_path: fp.bool('libs-only-L', `L`, false, 'show only -L from ldflags')
libs_only_other: fp.bool('libs-only-other', ` `, false, 'show flags not containing -l or -L')
args: fp.args
}
}

View File

@@ -0,0 +1,257 @@
module pkgconfig
import semver
import os
const (
default_paths = [
'/usr/local/lib/x86_64-linux-gnu/pkgconfig',
'/usr/local/lib/pkgconfig',
'/usr/local/share/pkgconfig',
'/usr/lib/x86_64-linux-gnu/pkgconfig',
'/usr/lib/pkgconfig',
'/usr/share/pkgconfig',
]
version = '0.2.0'
)
pub struct Options {
pub:
path string
debug bool
norecurse bool
}
pub struct PkgConfig {
pub mut:
options Options
libs []string
libs_private []string
cflags []string
paths []string // TODO: move to options?
vars map[string]string
requires []string
version string
description string
name string
modname string
}
fn (mut pc PkgConfig) parse_list(s string) []string {
operators := [ '=', '<', '>', '>=', '<=' ]
r := pc.parse_line(s.replace(',', '')).split(' ')
mut res := []string{}
mut skip := false
for a in r {
b := a.trim_space()
if skip {
skip = false
} else if b in operators {
skip = true
} else if b != '' {
res << b
}
}
return res
}
fn (mut pc PkgConfig) parse_line(s string) string {
mut r := s.trim_space()
for r.contains('\${') {
tok0 := r.index('\${') or {
break
}
mut tok1 := r[tok0..].index('}') or {
break
}
tok1 += tok0
v := r[tok0 + 2..tok1]
r = r.replace('\${$v}', pc.vars[v])
}
return r.trim_space()
}
fn (mut pc PkgConfig) setvar(line string) {
kv := line.trim_space().split('=')
if kv.len == 2 {
k := kv[0]
v := pc.parse_line(kv[1])
pc.vars[k] = pc.parse_line(v)
}
}
fn (mut pc PkgConfig) parse(file string) bool {
data := os.read_file(file) or {
return false
}
if pc.options.debug {
eprintln(data)
}
lines := data.split('\n')
if pc.options.norecurse {
// 2x faster than original pkg-config for --list-all --description
// TODO: use different variable. norecurse have nothing to do with this
for line in lines {
if line.starts_with('Description: ') {
pc.description = pc.parse_line(line[13..])
}
}
} else {
for line in lines {
if line.starts_with('#') {
continue
}
if line.contains('=') && !line.contains(' ') {
pc.setvar(line)
continue
}
if line.starts_with('Description:') {
pc.description = pc.parse_line(line[12..])
} else if line.starts_with('Name:') {
pc.name = pc.parse_line(line[5..])
} else if line.starts_with('Version:') {
pc.version = pc.parse_line(line[8..])
} else if line.starts_with('Requires:') {
pc.requires = pc.parse_list(line[9..])
} else if line.starts_with('Cflags:') {
pc.cflags = pc.parse_list(line[7..])
} else if line.starts_with('Libs:') {
pc.libs = pc.parse_list(line[5..])
} else if line.starts_with('Libs.private:') {
pc.libs_private = pc.parse_list(line[13..])
}
}
}
return true
}
fn (mut pc PkgConfig) resolve(pkgname string) ?string {
if pc.paths.len == 0 {
pc.paths << '.'
}
for path in pc.paths {
file := '$path/${pkgname}.pc'
if os.exists(file) {
return file
}
}
return error('Cannot find "$pkgname" pkgconfig file')
}
pub fn atleast(v string) bool {
v0 := semver.from(version) or {
return false
}
v1 := semver.from(v) or {
return false
}
return v0.gt(v1)
}
pub fn (mut pc PkgConfig) atleast(v string) bool {
v0 := semver.from(pc.version) or {
return false
}
v1 := semver.from(v) or {
return false
}
return v0.gt(v1)
}
pub fn (mut pc PkgConfig) extend(pcdep &PkgConfig) ?string {
for flag in pcdep.cflags {
if pc.cflags.index(flag) == -1 {
pc.cflags << flag
}
}
for lib in pcdep.libs {
if pc.libs.index(lib) == -1 {
pc.libs << lib
}
}
for lib in pcdep.libs_private {
if pc.libs_private.index(lib) == -1 {
pc.libs_private << lib
}
}
}
fn (mut pc PkgConfig) load_requires() {
for dep in pc.requires {
mut pcdep := PkgConfig{
paths: pc.paths
}
depfile := pcdep.resolve(dep) or {
break
}
pcdep.parse(depfile)
pcdep.load_requires()
pc.extend(pcdep)
}
}
fn (mut pc PkgConfig) add_path(path string) {
p := if path.ends_with('/') { path[0..path.len - 1] } else { path }
if pc.paths.index(p) == -1 {
pc.paths << p
}
}
fn (mut pc PkgConfig) load_paths() {
for path in default_paths {
pc.add_path(path)
}
for path in pc.options.path.split(':') {
pc.add_path(path)
}
env_var := os.getenv('PKG_CONFIG_PATH')
if env_var != '' {
env_paths := env_var.trim_space().split(':')
for path in env_paths {
pc.add_path(path)
}
}
}
pub fn load(pkgname string, options Options) ?&PkgConfig {
mut pc := &PkgConfig{
modname: pkgname
options: options
}
pc.load_paths()
file := pc.resolve(pkgname) or {
return error(err)
}
pc.parse(file)
/*
if pc.name != pc.modname {
eprintln('Warning: modname and filename differ $pc.name $pc.modname')
}
*/
if !options.norecurse {
pc.load_requires()
}
return pc
}
pub fn list() []string {
mut pc := &PkgConfig{
options: Options{}
}
pc.load_paths()
mut modules := []string{}
for path in pc.paths {
files := os.ls(path) or {
continue
}
for file in files {
if file.ends_with('.pc') {
name := file.replace('.pc', '')
if modules.index(name) == -1 {
modules << name
}
}
}
}
return modules
}

12
vlib/v/pkgconfig/v.mod Normal file
View File

@@ -0,0 +1,12 @@
Module {
name: 'pkgconfig'
author: 'pancake'
version: '0.2.0'
repo_url: 'https://github.com/trufae/v-pkgconfig'
vcs: 'git'
tags: ['system', 'compilers']
dependencies: ['semver']
description: 'V API implementing pkg-config logic'
license: 'MIT'
}