pkgconfig,flag: add tests to pkgconfig, fix bugs and links, improve flag.usage()

pull/8137/head
Delyan Angelov 2021-01-16 11:57:34 +02:00
parent 53941c4a0a
commit 1a8a1ceb0a
No known key found for this signature in database
GPG Key ID: 66886C0F12D595ED
20 changed files with 366 additions and 91 deletions

View File

@ -402,7 +402,7 @@ pub fn (fs FlagParser) usage() string {
use += '$option_names$xspace$f.usage\n'
}
}
return use
return use.replace('- ,', ' ')
}
// finalize argument parsing -> call after all arguments are defined

View File

@ -1,4 +1,4 @@
v-pkgconfig
v.pkgconfig
===========
This module implements the `pkg-config` tool as a library in pure V.
@ -11,21 +11,21 @@ Features:
* Resolve full path for `.pc` file given a name
* Recursively parse all the dependencies
* Find and replace all inner variables
* Integration with V, so you can just do `#pkgconfig r_core`
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`
The commandline tool is available in `vlib/v/pkgconfig/bin/pkgconfig.v`
```
$ ./bin/pkgconfig -h
pkgconfig 0.2.0
pkgconfig 0.3.0
-----------------------------------------------
Usage: pkgconfig [options] [ARGS]
@ -54,13 +54,14 @@ Options:
$
```
Using the API from the V repl
Using the API in your own programs:
```v
import v.pkgconfig
opt := pkgconfig.Options{}
mut pc := pkgconfig.load('expat', opt) or { panic(err) }
println(pc.libs)
```
>>> 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']
>>>
... will produce something like this:
```
['-L/usr/lib/x86_64-linux-gnu', '-lexpat']
```

View File

@ -1,7 +1,7 @@
module main
import v.pkgconfig
import os
import v.pkgconfig
fn main() {
mut m := pkgconfig.main(os.args[1..]) or {

View File

@ -3,7 +3,7 @@ module pkgconfig
import flag
import strings
pub struct Main {
struct Main {
pub mut:
opt &MainOptions
res string
@ -36,11 +36,9 @@ struct MainOptions {
fn desc(mod string) ?string {
options := Options{
norecurse: true
}
mut pc := load(mod, options) or {
return error('cannot parse')
only_description: true
}
mut pc := load(mod, options) or { return error('cannot parse') }
return pc.description
}
@ -53,7 +51,7 @@ pub fn main(args []string) ?&Main {
}
opt := m.opt
if opt.help {
m.res = fp.usage().replace('- ,', ' ')
m.res = fp.usage()
} else if opt.version {
m.res = version
} else if opt.listall {
@ -61,9 +59,7 @@ pub fn main(args []string) ?&Main {
modules.sort()
if opt.description {
for mod in modules {
d := desc(mod) or {
continue
}
d := desc(mod) or { continue }
pad := strings.repeat(` `, 20 - mod.len)
m.res += '$mod $pad $d\n'
}
@ -125,9 +121,7 @@ pub fn (mut m Main) run() ?string {
return res
}
if opt.variables {
for k, _ in pc.vars {
res += '$k\n'
}
res = pc.vars.keys().join('\n')
}
if opt.requires {
res += pc.requires.join('\n')

View File

@ -12,35 +12,39 @@ const (
'/usr/lib/pkgconfig',
'/usr/share/pkgconfig',
]
version = '0.2.0'
version = '0.3.0'
)
pub struct Options {
pub:
path string
debug bool
norecurse bool
path string
debug bool
norecurse bool
only_description bool
use_default_paths bool = true
}
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
options Options
name string
modname string
url string
version string
description string
libs []string
libs_private []string
cflags []string
paths []string // TODO: move to options?
vars map[string]string
requires []string
requires_private []string
version string
description string
name string
modname string
conflicts []string
}
fn (mut pc PkgConfig) parse_list(s string) []string {
operators := [ '=', '<', '>', '>=', '<=' ]
r := pc.parse_line(s.replace(' ', ' ').replace(',', '')).split(' ')
operators := ['=', '<', '>', '>=', '<=']
r := pc.parse_line(s.replace(' ', ' ').replace(', ', ' ')).split(' ')
mut res := []string{}
mut skip := false
for a in r {
@ -59,12 +63,8 @@ fn (mut pc PkgConfig) parse_list(s string) []string {
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
}
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])
@ -82,16 +82,13 @@ fn (mut pc PkgConfig) setvar(line string) {
}
fn (mut pc PkgConfig) parse(file string) bool {
data := os.read_file(file) or {
return false
}
data := os.read_file(file) or { return false }
if pc.options.debug {
eprintln(data)
}
lines := data.split('\n')
if pc.options.norecurse {
if pc.options.only_description {
// 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..])
@ -106,22 +103,26 @@ fn (mut pc PkgConfig) parse(file string) bool {
pc.setvar(line)
continue
}
if line.starts_with('Description:') {
pc.description = pc.parse_line(line[12..])
} else if line.starts_with('Name:') {
if line.starts_with('Name:') {
pc.name = pc.parse_line(line[5..])
} else if line.starts_with('Description:') {
pc.description = pc.parse_line(line[12..])
} 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('Requires.private:') {
pc.requires_private = pc.parse_list(line[17..])
} else if line.starts_with('Conflicts:') {
pc.conflicts = pc.parse_list(line[10..])
} 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..])
} else if line.starts_with('URL:') {
pc.url = pc.parse_line(line[4..])
}
}
}
@ -142,22 +143,14 @@ fn (mut pc PkgConfig) resolve(pkgname string) ?string {
}
pub fn atleast(v string) bool {
v0 := semver.from(version) or {
return false
}
v1 := semver.from(v) or {
return false
}
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
}
v0 := semver.from(pc.version) or { return false }
v1 := semver.from(v) or { return false }
return v0.gt(v1)
}
@ -180,38 +173,47 @@ pub fn (mut pc PkgConfig) extend(pcdep &PkgConfig) ?string {
return none
}
fn (mut pc PkgConfig) load_requires() {
fn (mut pc PkgConfig) load_requires() ? {
for dep in pc.requires {
pc.load_require(dep)
pc.load_require(dep) ?
}
for dep in pc.requires_private {
pc.load_require(dep)
pc.load_require(dep) ?
}
}
fn (mut pc PkgConfig) load_require(dep string) {
fn (mut pc PkgConfig) load_require(dep string) ? {
mut pcdep := PkgConfig{
paths: pc.paths
}
depfile := pcdep.resolve(dep) or {
eprintln('cannot resolve $dep')
return
if pc.options.debug {
eprintln('cannot resolve $dep')
}
return error('could not resolve dependency $dep')
}
pcdep.parse(depfile)
pcdep.load_requires()
if !pcdep.parse(depfile) {
return error('required file "$depfile" could not be parsed')
}
pcdep.load_requires() or { return error(err) }
pc.extend(pcdep)
}
fn (mut pc PkgConfig) add_path(path string) {
p := if path.ends_with('/') { path[0..path.len - 1] } else { path }
if !os.exists(p) {
return
}
if pc.paths.index(p) == -1 {
pc.paths << p
}
}
fn (mut pc PkgConfig) load_paths() {
for path in default_paths {
pc.add_path(path)
if pc.options.use_default_paths {
for path in default_paths {
pc.add_path(path)
}
}
for path in pc.options.path.split(':') {
pc.add_path(path)
@ -231,17 +233,12 @@ pub fn load(pkgname string, options Options) ?&PkgConfig {
options: options
}
pc.load_paths()
file := pc.resolve(pkgname) or {
return error(err)
file := pc.resolve(pkgname) or { return error(err) }
if !pc.parse(file) {
return error('file "$file" could not be parsed')
}
pc.parse(file)
/*
if pc.name != pc.modname {
eprintln('Warning: modname and filename differ $pc.name $pc.modname')
}
*/
if !options.norecurse {
pc.load_requires()
pc.load_requires() ?
}
return pc
}
@ -253,9 +250,7 @@ pub fn list() []string {
pc.load_paths()
mut modules := []string{}
for path in pc.paths {
files := os.ls(path) or {
continue
}
files := os.ls(path) or { continue }
for file in files {
if file.ends_with('.pc') {
name := file.replace('.pc', '')

View File

@ -0,0 +1,90 @@
import os
import pkgconfig
const vexe = os.getenv('VEXE')
const vroot = os.dir(vexe)
const samples_dir = os.join_path(vroot, 'vlib', 'v', 'pkgconfig', 'test_samples')
fn test_vexe_and_vroot_exist() {
assert vexe != ''
assert vroot != ''
assert os.is_file(vexe)
assert os.is_dir(vroot)
assert os.is_dir(samples_dir)
}
fn test_dependency_resolution_fails_correctly() {
pc_files := os.walk_ext(samples_dir, '.pc')
assert pc_files.len > 0
mut errors := []string{}
for pc in pc_files {
pcname := os.file_name(pc).replace('.pc', '')
pkgconfig.load(pcname, use_default_paths: false, path: samples_dir) or { errors << err }
}
assert errors.len < pc_files.len
assert errors == ['could not resolve dependency xyz-unknown-package']
}
fn test_samples() {
pc_files := os.walk_ext(samples_dir, '.pc')
assert pc_files.len > 0
for pc in pc_files {
pcname := os.file_name(pc).replace('.pc', '')
x := pkgconfig.load(pcname, use_default_paths: false, path: samples_dir) or {
if pcname == 'dep-resolution-fail' {
continue
}
println('>>> err: $err')
assert false
return
}
assert x.name != ''
assert x.modname != ''
assert x.version != ''
if pcname == 'gmodule-2.0' {
assert x.name == 'GModule'
assert x.modname == 'gmodule-2.0'
assert x.url == ''
assert x.version == '2.64.3'
assert x.description == 'Dynamic module loader for GLib'
assert x.libs ==
['-Wl,--export-dynamic', '-L/usr/lib/x86_64-linux-gnu', '-lgmodule-2.0', '-pthread', '-lglib-2.0', '-lpcre']
assert x.libs_private == ['-ldl', '-pthread']
assert x.cflags ==
['-I/usr/include', '-pthread', '-I/usr/include/glib-2.0', '-I/usr/lib/x86_64-linux-gnu/glib-2.0/include']
assert x.vars == {
'prefix': '/usr'
'libdir': '/usr/lib/x86_64-linux-gnu'
'includedir': '/usr/include'
'gmodule_supported': 'true'
}
assert x.requires == ['gmodule-no-export-2.0', 'glib-2.0']
assert x.requires_private == []
assert x.conflicts == []
}
if x.name == 'expat' {
assert x.url == 'http://www.libexpat.org'
}
if x.name == 'GLib' {
assert x.modname == 'glib-2.0'
assert x.libs == ['-L/usr/lib/x86_64-linux-gnu', '-lglib-2.0', '-lpcre']
assert x.libs_private == ['-pthread']
assert x.cflags ==
['-I/usr/include/glib-2.0', '-I/usr/lib/x86_64-linux-gnu/glib-2.0/include', '-I/usr/include']
assert x.vars == {
'prefix': '/usr'
'libdir': '/usr/lib/x86_64-linux-gnu'
'includedir': '/usr/include'
'bindir': '/usr/bin'
'glib_genmarshal': '/usr/bin/glib-genmarshal'
'gobject_query': '/usr/bin/gobject-query'
'glib_mkenums': '/usr/bin/glib-mkenums'
}
assert x.requires_private == ['libpcre']
assert x.version == '2.64.3'
assert x.conflicts == []
}
}
}

View File

@ -0,0 +1,12 @@
prefix=/usr
exec_prefix=/usr
libdir=${prefix}/lib/x86_64-linux-gnu
includedir=${prefix}/include
Name: alsa
Description: Advanced Linux Sound Architecture (ALSA) - Library
Version: 1.2.2
Requires:
Libs: -L${libdir} -lasound
Libs.private: -lm -ldl -lpthread -lrt
Cflags: -I${includedir}

View File

@ -0,0 +1,10 @@
prefix=/usr
libdir=${prefix}/lib/x86_64-linux-gnu
includedir=${prefix}/include
Name: Atk
Description: Accessibility Toolkit
Version: 2.35.1
Requires: glib-2.0 >= 2.38.0, gobject-2.0 >= 2.38.0
Libs: -L${libdir} -latk-1.0
Cflags: -I${includedir}/atk-1.0

View File

@ -0,0 +1,26 @@
# pkg-config information for AutoOpts 42.1.17
#
prefix="/usr"
datarootdir="/usr/share"
datadir="/usr/share"
package="autogen"
includedir="/usr/include"
exec_prefix="/usr"
bindir="/usr/bin"
libdir="/usr/lib/x86_64-linux-gnu"
ldopts=""
exeext=""
version="42:1:17"
dotver="42.1.17"
pkgdatadir="/usr/share/autogen"
autogen="/usr/bin/autogen"
libs="-lopts"
libsrc="/usr/share/autogen/libopts-42.1.17.tar.gz"
static_libs="/usr/lib/x86_64-linux-gnu/libopts.a"
Name: AutoOpts
Description: A semi-automated generated/library option parser
URL: http://www.gnu.org/software/autogen
Version: 42.1.17
Libs: -lopts
Cflags:

View File

@ -0,0 +1,7 @@
Name: impossible
Description: This is a package, that has an impossible dependency
Version: 1.2.2
Requires: xyz-unknown-package
Libs:
Libs.private: -lm
Cflags:

View File

@ -0,0 +1,11 @@
prefix=/usr
exec_prefix=${prefix}
libdir=${prefix}/lib/x86_64-linux-gnu
includedir=${prefix}/include
Name: expat
Version: 2.2.9
Description: expat XML parser
URL: http://www.libexpat.org
Libs: -L${libdir} -lexpat
Cflags: -I${includedir}

View File

@ -0,0 +1,19 @@
# pkg-config file generated by gen-pkgconfig
# vile:makemode
prefix=/usr
exec_prefix=${prefix}
libdir=/usr/lib/x86_64-linux-gnu
includedir=${prefix}/include
abi_version=6
major_version=6
version=6.2.20200212
Name: form
Description: ncurses 6.2 add-on library
Version: ${version}
URL: https://invisible-island.net/ncurses
Requires.private: ncurses
Libs: -Wl,-Bsymbolic-functions -lform
Libs.private:
Cflags: -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=600

View File

@ -0,0 +1,16 @@
prefix=/usr
libdir=${prefix}/lib/x86_64-linux-gnu
includedir=${prefix}/include
bindir=${prefix}/bin
glib_genmarshal=${bindir}/glib-genmarshal
gobject_query=${bindir}/gobject-query
glib_mkenums=${bindir}/glib-mkenums
Name: GLib
Description: C Utility Library
Version: 2.64.3
Requires.private: libpcre >= 8.31
Libs: -L${libdir} -lglib-2.0
Libs.private: -pthread
Cflags: -I${includedir}/glib-2.0 -I${libdir}/glib-2.0/include

View File

@ -0,0 +1,12 @@
prefix=/usr
libdir=${prefix}/lib/x86_64-linux-gnu
includedir=${prefix}/include
gmodule_supported=true
Name: GModule
Description: Dynamic module loader for GLib
Version: 2.64.3
Requires: gmodule-no-export-2.0, glib-2.0
Libs: -Wl,--export-dynamic
Cflags: -I${includedir}

View File

@ -0,0 +1,13 @@
prefix=/usr
libdir=${prefix}/lib/x86_64-linux-gnu
includedir=${prefix}/include
gmodule_supported=true
Name: GModule
Description: Dynamic module loader for GLib
Version: 2.64.3
Requires: glib-2.0
Libs: -L${libdir} -lgmodule-2.0 -pthread
Libs.private: -ldl
Cflags: -I${includedir} -pthread

View File

@ -0,0 +1,12 @@
prefix=/usr
libdir=${prefix}/lib/x86_64-linux-gnu
includedir=${prefix}/include
Name: GObject
Description: GLib Type, Object, Parameter and Signal Library
Version: 2.64.3
Requires: glib-2.0
Requires.private: libffi >= 3.0.0
Libs: -L${libdir} -lgobject-2.0
Libs.private: -pthread
Cflags: -I${includedir}

View File

@ -0,0 +1,10 @@
prefix=/usr
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include
Name: libffi
Description: Library supporting Foreign Function Interfaces
Version: 3.3
Libs: -lffi
Cflags: -I${includedir}

View File

@ -0,0 +1,13 @@
# Package Information for pkg-config
prefix=/usr
exec_prefix=${prefix}
libdir=${prefix}/lib/x86_64-linux-gnu
includedir=${prefix}/include
Name: libpcre
Description: PCRE - Perl compatible regular expressions C library with 8 bit character support
Version: 8.39
Libs: -L${libdir} -lpcre
Libs.private: -pthread
Cflags: -I${includedir}

View File

@ -0,0 +1,19 @@
# pkg-config file generated by gen-pkgconfig
# vile:makemode
prefix=/usr
exec_prefix=${prefix}
libdir=/usr/lib/x86_64-linux-gnu
includedir=${prefix}/include
abi_version=6
major_version=6
version=6.2.20200212
Name: ncurses
Description: ncurses 6.2 library
Version: ${version}
URL: https://invisible-island.net/ncurses
Requires.private:
Libs: -Wl,-Bsymbolic-functions -lncurses -ltinfo
Libs.private: -ldl
Cflags: -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=600

View File

@ -0,0 +1,15 @@
# sdl pkg-config source file
prefix=/usr
exec_prefix=${prefix}
libdir=${prefix}/lib/x86_64-linux-gnu
includedir=${prefix}/include
Name: sdl2
Description: Simple DirectMedia Layer is a cross-platform multimedia library designed to provide low level access to audio, keyboard, mouse, joystick, 3D hardware via OpenGL, and 2D video framebuffer.
Version: 2.0.10
Requires:
Conflicts:
Libs: -L${libdir} -lSDL2
Libs.private: -lSDL2 -Wl,--no-undefined -lm -ldl -lasound -lm -ldl -lpthread -lpulse-simple -lpulse -lX11 -lXext -lXcursor -lXinerama -lXi -lXrandr -lXss -lXxf86vm -lwayland-egl -lwayland-client -lwayland-cursor -lxkbcommon -lpthread -lrt
Cflags: -I${includedir}/SDL2 -D_REENTRANT