Skip to content

Conversation

@KDr2
Copy link
Member

@KDr2 KDr2 commented Apr 2, 2020

No description provided.

@KDr2 KDr2 force-pushed the prebuilt-binary branch from 996d8e4 to c2f7e65 Compare April 2, 2020 02:20
@KDr2
Copy link
Member Author

KDr2 commented Apr 2, 2020

Now the build script downloads binaries from https://github.com/JuliaBinaryWrappers/Libtask_jll.jl/releases, I think this will fix #53 and #57.

@KDr2 KDr2 force-pushed the prebuilt-binary branch 2 times, most recently from de40af6 to 14531ff Compare April 2, 2020 02:27
@KDr2 KDr2 force-pushed the prebuilt-binary branch from 14531ff to a18e2dd Compare April 2, 2020 02:27
@KDr2 KDr2 requested a review from devmotion April 2, 2020 02:31
@devmotion
Copy link
Member

I don't think this is the ideal solution yet since we still have to keep the build_dylib.sh and the build_tarballs.jl files around (which BTW are different from the ones that are checked in Yggdrasil - so they have to be synced manually) and have some rudimentary build CI tests to make sure that changes to the build files don't break anything. Maybe libtask.c and Makefile should be moved to Yggdrasil (and maybe Makefile even just integrated in the build_tarballs.jl script)? But I guess we can keep iterating until we find a better solution.

In general, I think we could just remove the commented out or unneeded parts since they are still available in the git history anyways. In the build Github action the tagging part is not needed either anymore I guess? At least I think it caused problems in the last PR. I.e., I would suggest removing at least deps/gh-auto-tag and deps/gh-update-to-release.

Maybe we could also switch from building binaries in every PR to just building binaries if one of the files deps/Makefile, deps/libtask.c, or deps/build_tarballs.jl changed using https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#example-including-paths (we do not have to build binaries if only deps/build.jl changed, so I specified the files explicitly):

on:
  push:
    paths:
    - 'deps/Makefile'
    - 'deps/libtask.c'
    - 'deps/build_tarballs.jl'
    - 'deps/build_dylib.sh'

The Julia integration, however, won't be tested (Libtask.jl will still use the binaries served from Libtask_jll), so maybe it would actually make sense to move these files to Yggdrasil. Additionally, maybe the build CI should run with a newer Julia version?

I also ran the script to generate the build.jl file, as suggested in the docs of Yggdrasil (with Julia 1.3). Initially it failed due to the use of @__FILE__ in the build_tarballs.jl file in Yggdrasil. I think the script in Yggdrasil should be updated and build_dylib.sh should be added to the list of sources. After fixing that it worked for me, so IMO we don't need the script for retrieving the hashes (that's done automatically by the script in Yggdrasil) and we could rather stick to the generated build.jl and add an additional function for filtering the current version.

@devmotion
Copy link
Member

BTW the logic for picking the correct version depending on the Julia version could also be moved to src/Libtask.jl. I guess that might be needed anyways when moving to the artifacts system at some point, since then loading using Libtask_jll would enable the use of all libtask libraries? I'm also wondering, is the current approach of filtering based on the Julia version problematic when switching between different Julia versions, i.e., if deps/deps.jl was generated with Julia 1.3, is it updated when Libtask is loaded with Julia 1.2 the next time?

@KDr2
Copy link
Member Author

KDr2 commented Apr 2, 2020

You are right, I did some updates of this PR:

  • move the version selection to deps.jl
  • remove unused files, update the DylibBuild workflow to make it only run on relevant files updated

About the artifacts system and using *_jll, I think maybe we could not adopt it before giving up the supports of Julia 1.2 and earlier, so let's use the build.jl and deps.jl for the time beings?

About where to place the source and build scripts (task.c Makefile, build_tarballs.jl, dylib_build.sh), I didn't find an ideal way either. No one puts code source to Yggdrasil, and a separate repo for it is also too heavy because we don't need one to release and host the binary lib now.

@KDr2 KDr2 force-pushed the prebuilt-binary branch from 2ba4d20 to f9434a7 Compare April 2, 2020 08:53
@KDr2 KDr2 force-pushed the prebuilt-binary branch from f9434a7 to 0cb3275 Compare April 2, 2020 09:02
@KDr2
Copy link
Member Author

KDr2 commented Apr 2, 2020

About generating build.jl using the script provided by Yggdrasil, I can't run it either,
On Julia 1.5:

$ julia generate_buildjl.jl L/Libtask/build_tarballs.jl
[ Info: Build tarballs script: L/Libtask/build_tarballs.jl
[ Info: Repo name: JuliaBinaryWrappers/Libtask_jll.jl
   Updating registry at `~/.julia/registries/General`
   Updating git-repo `https://github.com/JuliaRegistries/General.git`
ERROR: LoadError: MethodError: no method matching registered_paths(::Pkg.Types.EnvCache, ::Base.UUID)
Closest candidates are:
  registered_paths(::Pkg.Types.Context, ::Base.UUID) at /home/kdr2/Work/julia/julia/usr/share/julia/stdlib/v1.5/Pkg/src/Types.jl:1190
Stacktrace:
 [1] top-level scope at /home/kdr2/Work/julia/Yggdrasil/generate_buildjl.jl:164
 [2] include(::Function, ::Module, ::String) at ./Base.jl:381
 [3] include(::Module, ::String) at ./Base.jl:369
 [4] exec_options(::Base.JLOptions) at ./client.jl:289
 [5] _start() at ./client.jl:491
in expression starting at /home/kdr2/Work/julia/Yggdrasil/generate_buildjl.jl:157

On Julia 1.3:

l$ julia generate_buildjl.jl L/Libtask/build_tarballs.jl
ERROR: LoadError: UndefVarError: BinaryPlatforms not defined
Stacktrace:
 [1] include at ./boot.jl:328 [inlined]
 [2] include_relative(::Module, ::String) at ./loading.jl:1094
 [3] include(::Module, ::String) at ./Base.jl:31
 [4] top-level scope at none:2
 [5] eval at ./boot.jl:330 [inlined]
 [6] eval(::Expr) at ./client.jl:433
 [7] top-level scope at ./none:3
in expression starting at /home/kdr2/.julia/packages/BinaryBuilder/o9Hrl/src/BinaryBuilder.jl:6
ERROR: LoadError: Failed to precompile BinaryBuilder [12aac903-9f7c-5d81-afc2-d9565ea332ae] to /home/kdr2/.julia/compiled/v1.3/BinaryBuilder/WHzgM.ji.
Stacktrace:
 [1] error(::String) at ./error.jl:33
 [2] compilecache(::Base.PkgId, ::String) at ./loading.jl:1253
 [3] _require(::Base.PkgId) at ./loading.jl:1013
 [4] require(::Base.PkgId) at ./loading.jl:911
 [5] require(::Module, ::Symbol) at ./loading.jl:906
 [6] include at ./boot.jl:328 [inlined]
 [7] include_relative(::Module, ::String) at ./loading.jl:1094
 [8] include(::Module, ::String) at ./Base.jl:31
 [9] exec_options(::Base.JLOptions) at ./client.jl:295
 [10] _start() at ./client.jl:468
in expression starting at /home/kdr2/Work/julia/Yggdrasil/generate_buildjl.jl:3

But we I think we should modify the generate build.jl even if that script runs successfully, unless we figure out a better way to deal with the version selection of the libs.

@devmotion
Copy link
Member

About generating build.jl using the script provided by Yggdrasil, I can't run it either

It worked on Julia 1.3 for me (after fixing the problem with @__FILE__, as I mentioned). Maybe you have to update your environment or some packages on Julia 1.3? I used a local environment and just added BinraryBuilder.

name: TagBot
on:
schedule:
- cron: 0 * * * *
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You still want to keep TagBot.yml since that will tag a new release when the PRs to the registry are merged. Maybe just change it to once per day (I think it's not needed to run it hourly):

     - cron: '0 0 * * *'

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, not intentionally to delete this, I confused it with the auto-tag job, updated in a new commit.

@devmotion
Copy link
Member

You are right, I did some updates of this PR:

move the version selection to deps.jl
remove unused files, update the DylibBuild workflow to make it only run on relevant files updated

About the artifacts system and using *_jll, I think maybe we could not adopt it before giving up the supports of Julia 1.2 and earlier, so let's use the build.jl and deps.jl for the time beings?

Sure, for the time being I guess it's easiest if we keep using the buid.jl and deps.jl files instead of hacking something together.

However, I think the version selection should not be moved to deps.jl but rather to src/Libtask.jl. IMO that has some advantages:

  • We can just generate the standard deps/build.jl file and so update it easily if there is a new release of Libtask_jll.
  • If we don't use it yet, the current setup in deps/build.jl won't work with Libtask_jll whereas the other approach does.

So I think we should just add the following to src/Libtask.jl after loading the deps/deps.jl file (basically moving from taskcopy.jl):

@static VERSION >= v"1.4"
	const libtask = libtask_1_4
elseif VERSION >= v"1.3"
	const libtask = libtask_1_3
elseif VERSION >= v"1.2"
	const libtask = libtask_1_2
elseif VERSION >= v"1.1"
	const libtask = libtask_1_1
else
	const libtask = libtask_1_0
end

@devmotion
Copy link
Member

About where to place the source and build scripts (task.c Makefile, build_tarballs.jl, dylib_build.sh), I didn't find an ideal way either. No one puts code source to Yggdrasil, and a separate repo for it is also too heavy because we don't need one to release and host the binary lib now.

Yes, most packages download source code from some external repository but it's very common to include patches in the subdirectory ./bundled in Yggdrasil. And some packages even include source files, e.g., https://github.com/JuliaPackaging/Yggdrasil/blob/c9d46c4474502496a9933bf577bb5a69b8caf94e/S/SuiteSparse/bundled/SuiteSparse_wrapper/SuiteSparse_wrapper.c. Typically the build_dylib.sh is just written out explicitly in build_tarballs.jl when defining the script variable. The Makefile is often part of the external source code, though, but it just seems to set flags for the compiler which is typically also done when defining the script variable. So maybe both build_dylib.sh and the Makefile could actually be removed.

@KDr2
Copy link
Member Author

KDr2 commented Apr 2, 2020

It has an issue putting the version selection to Libtask.jl and keep the generated deps.jl untouched:
In deps.jl, it will try to load every lib file and test them (libtask_v1_0, *v1_1...) which may cause errors, e.g., when Julia 1.3 is used, it will fail to load libtask_v1_0 (API not compatible, what is the reason we need multi-version libs). So we must update the deps.jl file to avoid this.

@KDr2
Copy link
Member Author

KDr2 commented Apr 2, 2020

Now, I managed to generate build.jl by running julia generate_buildjl.jl L/Libtask/build_tarballs.jl, then make this commit: bd06877, but found this generated build.jl is not compatible with some version of Julia, see
https://github.com/TuringLang/Libtask.jl/pull/58/checks?check_run_id=554626983#step:4:34

So I decided to revert the commit and use my version. now:

  • build and host binary within Yggdrasil
  • use a (manual update but need not update frequently) build.jl to generate deps.jl.
  • select lib version in deps.jl to avoid error on version mismatch

Is this OK?

@devmotion
Copy link
Member

In deps.jl, it will try to load every lib file and test them (libtask_v1_0, *v1_1...) which may cause errors, e.g., when Julia 1.3 is used, it will fail to load libtask_v1_0 (API not compatible, what is the reason we need multi-version libs). So we must update the deps.jl file to avoid this.

Isn't the only check loading the libraries? Surely the incompatible libraries can't be used but would it even fail to open the libraries?

The third reason for why I thought it might be preferable to not handle the selection in the Pkg.build step is that I'm not sure if packages are rebuilt automatically if you switch Julia versions (since I guess usually binaries only depend on the OS). Do you know if this is the case? I think it would be a bit unfortunate if everyone has to rebuild Libtask manually everytime one switches Julia versions, even if it's e.g. just a dependency of Turing.

@KDr2
Copy link
Member Author

KDr2 commented Apr 2, 2020

Isn't the only check loading the libraries? Surely the incompatible libraries can't be used but would it even fail to open the libraries?

Yeah, it checks if every lib file exists and try to load them by calling dlopen, dlopen throws errors when symbols can't be resolved, so I used a product filter to modify the generated build.jl(https://github.com/TuringLang/Libtask.jl/blob/master/deps/build.jl#L12) to eliminate the incompatible libs.

@devmotion
Copy link
Member

The errors are unrelated and explained in the Yggdrasil README:

Note: you have to manually add prefix as the first argument to all Product constructors in the generated build.jl files. This is necessary because the syntax between BinaryBuilder v0.2+ and BinaryProvider has diverged.

After adding prefix to the LibraryProduct calls (i.e., changing it to LibraryProduct(prefix, ....)), I am able to build and load Libtask with Julia 1.0, 1.1, 1.2, 1.3, and 1.4..

@KDr2 KDr2 force-pushed the prebuilt-binary branch from 79e6bae to 303bca8 Compare April 2, 2020 11:23
deps/build.jl Outdated
Comment on lines 54 to 83
lib_versions = map(p -> p.libnames[1][9:end], products)
lib_path = Sys.iswindows() ? "bin" : "lib"
deps_source = """
if isdefined((@static VERSION < v"0.7.0-DEV.484" ? current_module() : @__MODULE__), :Compat)
import Compat.Libdl
elseif VERSION >= v"0.7.0-DEV.3382"
import Libdl
end
function include_build_script(version_str, try_prev=false)
build_script_url = "https://github.com/TuringLang/Libtask.jl/releases/download/v$(version_str)/build_LibtaskDylib.v$(version_str).jl"
build_script = joinpath(@__DIR__, "tmp-build.jl")
build_script = try download(build_script_url, build_script) catch end
if build_script == nothing && try_prev # no such file
version_str = find_prev_tag("v$version_str") |> strip |> (x) -> lstrip(x, ['v'])
return include_build_script(version_str, false)
end
install_products_filter(build_script)
include(build_script)
const lib_versions = $(lib_versions)
proper_ver = filter(lib_versions) do v
endswith(v, "\$(VERSION.major)_\$(VERSION.minor)")
end
proper_ver = length(proper_ver) == 0 ? lib_versions[end] : proper_ver[1]
const libtask = joinpath(dirname(@__FILE__), "usr/$(lib_path)/libtask_\$(proper_ver).\$(Libdl.dlext)")
function get_version_str()
path = joinpath(@__DIR__, "../Project.toml")
version_reg = r"version\s*=\s*\"(.*)\""
open(path) do file
lines = readlines(file)
for line in lines
m = match(version_reg, line)
if isa(m, RegexMatch) return m.captures[1] end
end
function check_deps()
global libtask
if !isfile(libtask)
error("\$(libtask) does not exist, Please re-run Pkg.build(\\"Libtask.jl\\"), and restart Julia.")
end
if Libdl.dlopen_e(libtask) in (C_NULL, nothing)
error("\$(libtask) cannot be opened, Please re-run Pkg.build(\\"Libtask.jl\\"), and restart Julia.")
end
end
version_str = get_version_str() |> strip |> (x) -> lstrip(x, ['v'])
include_build_script(version_str, true)
"""
write(joinpath(@__DIR__, "deps.jl"), deps_source)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think if possible we should not write deps.jl explicitly. For instance, if you compare this with the implementation of write_deps_file in https://github.com/JuliaPackaging/BinaryProvider.jl/blob/81f03dad25dfd76845f4fbc0eea1a383280f334c/src/Products.jl#L397-L507 which is usually used in builds.jl, you see that in the standard version there are many more checks and a much more complicated logic for deriving the correct paths on each OS. IMO if possible we shouldn't try to outsmart this implementation.

If we want a custom check_deps() function it might be better to just add a custom check in src/Libtask.jl after including deps/deps.jl and not running check_deps().

@devmotion
Copy link
Member

So I think the following approach could work:

  • Use the generated build.jl and just add prefix to the LibraryProduct definitions
  • Define the const libtask as described above in src/Libtask.jl
  • in the __init__() method in src/Libtask.jl just check if libtask can be loaded in the same way as it's currently done in deps/deps.jl, i.e., copy the check that you added to the custom build script.

In that way we don't mess with the generation of the deps.jl file (in particular not with paths) but still just check and use the version that we want to use.

src/Libtask.jl Outdated

if Libdl.dlopen_e(libtask) in (C_NULL, nothing)
error("$(libtask) cannot be opened, Please re-run Pkg.build(\"Libtask.jl\"), and restart Julia.")
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest you move the checks to an __init__() method so that they are only executed once after the module has been loaded. See https://github.com/JuliaPackaging/BinaryProvider.jl/blob/master/test/LibFoo.jl/src/LibFoo.jl for an example setup.

BTW with the additional definitions of libtask inow it might might be easier to just check if the file does not exist, as shown in the example linked above, and remove the second branch, i.e., just execute everything if it didn't error.

@KDr2
Copy link
Member Author

KDr2 commented Apr 2, 2020

I am sorry that this way doesn't work...

build.jl also checks the libs using dlopen in the function write_deps_file, it fails on macOS, and won't write deps.jl. This is the error: https://github.com/TuringLang/Libtask.jl/pull/58/checks?check_run_id=554895456#step:4:35

So we have to modify build.jl to avoid calling write_deps_file.

@devmotion
Copy link
Member

Yes due to line https://github.com/JuliaPackaging/BinaryProvider.jl/blob/55aeaea2652c1b43ba882c2f833196e99f10e2db/src/Products.jl#L418

IMO then the second best solution would be to filter the correct product before calling write_deps_file. I really think copying or modifying the implementation of write_deps_file should be avoided at any means if possible.

@KDr2
Copy link
Member Author

KDr2 commented Apr 2, 2020

IMO then the second best solution would be to filter the correct product before calling write_deps_file. I really think copying or modifying the implementation of write_deps_file should be avoided at any means if possible.

OK, they don't make a big difference to me, I will filter the products list before call write_deps_file :)

Comment on lines +21 to 25
products_tmp = filter(products) do prod
endswith(prod.libnames[1], "$(VERSION.major)_$(VERSION.minor)")
end
length(products_tmp) == 0 && (products_tmp = [products[end]])
products = products_tmp
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, I guess we don't have to care about version jumps? In that case, something like

Suggested change
products_tmp = filter(products) do prod
endswith(prod.libnames[1], "$(VERSION.major)_$(VERSION.minor)")
end
length(products_tmp) == 0 && (products_tmp = [products[end]])
products = products_tmp
lastminor = findlast(products) do p
x = match(r"v(\d)_(\d)$", string(p.variable_name))
x !== nothing && VERSION.major >= parse(Int, x.captures[1]) && VERSION.minor >= parse(Int, x.captures[2])
end
products = products[[lastminor]]

would provide a the last compatible version.

src/Libtask.jl Outdated
elseif VERSION < v"1.2.9999" # [v1.2, v1.3)
const libtask = libtask_v1_2
else # [v1.3, +)
const libtask = libtask_v1_3
Copy link
Member

@devmotion devmotion Apr 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const libtask = libtask_v1_3
const libtask = libtask_v1_3

@KDr2 KDr2 force-pushed the prebuilt-binary branch from 8aa1df2 to d7c057a Compare April 2, 2020 13:12
@KDr2 KDr2 merged commit 806240c into master Apr 2, 2020
@delete-merged-branch delete-merged-branch bot deleted the prebuilt-binary branch April 2, 2020 13:12
@KDr2
Copy link
Member Author

KDr2 commented Apr 2, 2020

Thank you @devmotion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants