diff --git a/.JuliaFormatter.toml b/.JuliaFormatter.toml new file mode 100644 index 0000000..15fa654 --- /dev/null +++ b/.JuliaFormatter.toml @@ -0,0 +1,7 @@ +margin = 100 +indent = 2 +whitespace_typedefs = true +whitespace_ops_in_indices = true +remove_extra_newlines = true +annotate_untyped_fields_with_any = false +normalize_line_endings = "unix" \ No newline at end of file diff --git a/.cirrus.yml b/.cirrus.yml index b2fcc1f..ffd8c12 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -1,17 +1,17 @@ freebsd_instance: - image: freebsd-12-0-release-amd64 + image: freebsd-12-1-release-amd64 task: name: FreeBSD artifacts_cache: folder: ~/.julia/artifacts env: - JULIA_VERSION: 1.6 - JULIA_VERSION: nightly + matrix: + JULIA_VERSION: 1 + JULIA_VERSION: nightly + allow_failures: $JULIA_VERSION == 'nightly' install_script: - sh -c "$(fetch https://raw.githubusercontent.com/ararslan/CirrusCI.jl/master/bin/install.sh -o -)" build_script: - cirrusjl build test_script: - cirrusjl test - coverage_script: - - cirrusjl coverage codecov diff --git a/.github/workflows/format_pr.yml b/.github/workflows/format_pr.yml new file mode 100644 index 0000000..c8f5308 --- /dev/null +++ b/.github/workflows/format_pr.yml @@ -0,0 +1,32 @@ +# https://github.com/julia-actions/julia-format/blob/master/workflows/format_pr.yml +name: format-pr +on: + push: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install JuliaFormatter and format + run: | + julia -e 'import Pkg; Pkg.add("JuliaFormatter")' + julia -e 'using JuliaFormatter; format(".")' + # https://github.com/marketplace/actions/create-pull-request + # https://github.com/peter-evans/create-pull-request#reference-example + - name: Create Pull Request + id: cpr + uses: peter-evans/create-pull-request@v3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: ":robot: Format .jl files" + title: '[AUTO] JuliaFormatter.jl run' + branch: auto-juliaformatter-pr + delete-branch: true + labels: formatting, automated pr, no changelog + - name: Check outputs + run: | + echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}" + echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 20fe29d..75634c9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ *.jl.mem /Manifest.toml /docs/build/ +benchmark/Manifest.toml + diff --git a/CITATION.bib b/CITATION.bib deleted file mode 100644 index 30b4fb1..0000000 --- a/CITATION.bib +++ /dev/null @@ -1,8 +0,0 @@ -@misc{JSOTemplate.jl, - author = {Abel Soares Siqueira and contributors}, - title = {JSOTemplate.jl}, - url = {https://github.com/JuliaSmoothOptimizers/JSOTemplate.jl}, - version = {v0.1.0}, - year = {2021}, - month = {9} -} diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..27f34d8 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,10 @@ +cff-version: 1.2.0 +message: "If you use this software, please cite it as below." +authors: + - family-names: Template + given-names: JSO + orcid: https://orcid.org/0123-4567-89AB-CDEF +title: "JSO Template" +version: 2.0.4 +doi: 10.5281/zenodo.1234 +date-released: 2021-10-04 \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..ff97c74 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,119 @@ +def bmarkFile = 'run_benchmarks.jl' +def prNumber = BRANCH_NAME.tokenize("PR-")[0] +pipeline { + agent any + environment { + REPO_EXISTS = fileExists "$repo" + } + options { + skipDefaultCheckout true + } + triggers { + GenericTrigger( + genericVariables: [ + [ + key: 'action', + value: '$.action', + expressionType: 'JSONPath', //Optional, defaults to JSONPath + regexpFilter: '[^(created)]', //Optional, defaults to empty string + defaultValue: '' //Optional, defaults to empty string + ], + [ + key: 'comment', + value: '$.comment.body', + expressionType: 'JSONPath', //Optional, defaults to JSONPath + regexpFilter: '', //Optional, defaults to empty string + defaultValue: '' //Optional, defaults to empty string + ], + [ + key: 'org', + value: '$.organization.login', + expressionType: 'JSONPath', //Optional, defaults to JSONPath + regexpFilter: '', //Optional, defaults to empty string + defaultValue: 'JuliaSmoothOptimizers' //Optional, defaults to empty string + ], + [ + key: 'pullrequest', + value: '$.issue.number', + expressionType: 'JSONPath', //Optional, defaults to JSONPath + regexpFilter: '[^0-9]', //Optional, defaults to empty string + defaultValue: '' //Optional, defaults to empty string + ], + [ + key: 'repo', + value: '$.repository.name', + expressionType: 'JSONPath', //Optional, defaults to JSONPath + regexpFilter: '', //Optional, defaults to empty string + defaultValue: '' //Optional, defaults to empty string + ] + ], + + causeString: 'Triggered on $comment', + + token: "JSOTemplate", + + printContributedVariables: true, + printPostContent: true, + + silentResponse: false, + + regexpFilterText: '$comment $pullrequest', + regexpFilterExpression: '@JSOBot runbenchmarks( .*\\.jl)? ' + prNumber + ) + } + stages { + stage('clone repo') { + when { + expression { REPO_EXISTS == 'false' } + } + steps { + sh 'git clone https://${GITHUB_AUTH}@github.com/$org/$repo.git' + } + } + stage('checkout on new branch') { + steps { + dir(WORKSPACE + "/$repo") { + sh ''' + git clean -fd + git checkout main + git pull origin main + git fetch origin + LOCAL_BRANCH_NAME="temp_bmark" + git branch -D $LOCAL_BRANCH_NAME || true + git fetch origin pull/$pullrequest/head:$LOCAL_BRANCH_NAME + git checkout $LOCAL_BRANCH_NAME -- + ''' + } + } + } + stage('run benchmarks') { + steps { + script { + def data = env.comment.tokenize(' ') + if (data.size() > 2) { + bmarkFile = data.get(2); + } + } + dir(WORKSPACE + "/$repo") { + sh "mkdir -p $HOME/benchmarks/${org}/${repo}" + sh "qsub -N ${repo}_${pullrequest} -V -cwd -o $HOME/benchmarks/${org}/${repo}/${pullrequest}_${BUILD_NUMBER}_bmark_output.log -e $HOME/benchmarks/${org}/${repo}/${pullrequest}_${BUILD_NUMBER}_bmark_error.log push_benchmarks.sh $bmarkFile" + } + } + } + } + post { + success { + echo "SUCCESS!" + } + cleanup { + dir(WORKSPACE + "/$repo") { + sh 'printenv' + sh ''' + git clean -fd + git checkout main + ''' + } + } + } +} + diff --git a/benchmark/Project.toml b/benchmark/Project.toml new file mode 100644 index 0000000..6e9bdbc --- /dev/null +++ b/benchmark/Project.toml @@ -0,0 +1,12 @@ +[deps] +ArgParse = "c7e460c6-2fb9-53a9-8c5b-16f535851c63" +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" +Git = "d7ba0133-e1db-5d97-8f8c-041e4b3a1eb2" +GitHub = "bc5e4493-9b4d-5f90-b8aa-2b2bcaad7a26" +JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819" +JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +JSOTemplate = "adf32eb0-fe7e-47bb-93e4-fe96e72f6839" +PkgBenchmark = "32113eaa-f34f-5b0d-bd6c-c81e245fc73d" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +SolverBenchmark = "581a75fa-a23a-52d0-a590-d6201de2218a" diff --git a/benchmark/benchmarks.jl b/benchmark/benchmarks.jl new file mode 100644 index 0000000..4cdc353 --- /dev/null +++ b/benchmark/benchmarks.jl @@ -0,0 +1,5 @@ +using BenchmarkTools + +const SUITE = BenchmarkGroup() + +# Write your benchmarks here: \ No newline at end of file diff --git a/benchmark/run_benchmarks.jl b/benchmark/run_benchmarks.jl new file mode 100644 index 0000000..05434a2 --- /dev/null +++ b/benchmark/run_benchmarks.jl @@ -0,0 +1,85 @@ +using Pkg +bmark_dir = @__DIR__ +repo_name = string(split(ARGS[1], ".")[1]) +bmarkname = lowercase(repo_name) + +# if we are running these benchmarks from the git repository +# we want to develop the package instead of using the release +if isdir(joinpath(bmark_dir, "..", ".git")) + Pkg.develop(PackageSpec(url = joinpath(bmark_dir, ".."))) +end + +using DataFrames +using GitHub +using JLD2 +using JSON +using PkgBenchmark +using Plots + +using SolverBenchmark + +# NB: benchmarkpkg will run benchmarks/benchmarks.jl by default +commit = benchmarkpkg(repo_name) # current state of repository +main = benchmarkpkg(repo_name, "main") +judgement = judge(commit, main) + +commit_stats = bmark_results_to_dataframes(commit) +main_stats = bmark_results_to_dataframes(main) +judgement_stats = judgement_results_to_dataframes(judgement) + +export_markdown("judgement_$(bmarkname).md", judgement) +export_markdown("main.md", main) +export_markdown("$(bmarkname).md", commit) + +function profile_solvers_from_pkgbmark(stats::Dict{Symbol,DataFrame}) + # guard against zero gctimes + costs = [ + df -> df[!, :time], + df -> df[!, :memory], + df -> df[!, :gctime] .+ 1, + df -> df[!, :allocations], + ] + profile_solvers(stats, costs, ["time", "memory", "gctime+1", "allocations"]) +end + +# extract stats for each benchmark to plot profiles +# files_dict will be part of json_dict below +files_dict = Dict{String,Any}() +file_num = 1 +for k ∈ keys(judgement_stats) + global file_num + k_stats = Dict{Symbol,DataFrame}(:commit => commit_stats[k], :main => main_stats[k]) + save_stats(k_stats, "$(bmarkname)_vs_main_$(k).jld2", force = true) + + k_profile = profile_solvers_from_pkgbmark(k_stats) + savefig("profiles_commit_vs_main_$(k).svg") + # read contents of svg file to add to gist + k_svgfile = open("profiles_commit_vs_main_$(k).svg", "r") do fd + readlines(fd) + end + # file_num makes sure svg files appear before md files (added below) + files_dict["$(file_num)_$(k).svg"] = Dict{String,Any}("content" => join(k_svgfile)) + file_num += 1 +end + +for mdfile ∈ [:judgement, :main, :commit] + global file_num + files_dict["$(file_num)_$(mdfile).md"] = + Dict{String,Any}("content" => "$(sprint(export_markdown, eval(mdfile)))") + file_num += 1 +end + +jldopen("$(bmarkname)_vs_main_judgement.jld2", "w") do file + file["jstats"] = judgement_stats +end + +# json description of gist +json_dict = Dict{String,Any}( + "description" => "$(repo_name) repository benchmark", + "public" => true, + "files" => files_dict, +) + +open("gist.json", "w") do f + JSON.print(f, json_dict) +end diff --git a/benchmark/send_comment_to_pr.jl b/benchmark/send_comment_to_pr.jl new file mode 100644 index 0000000..f0b09fa --- /dev/null +++ b/benchmark/send_comment_to_pr.jl @@ -0,0 +1,137 @@ +using ArgParse +using Git +using GitHub +using JSON + +DEFAULT_GIST_FILE_PATH = "gist.json" + +function parse_commandline() + s = ArgParseSettings() + @add_arg_table! s begin + "--org", "-o" + help = "Name of GitHub Organization" + arg_type = String + default = "JuliaSmoothOptimizers" + "--repo", "-r" + help = "The name of the repository on GitHub" + arg_type = String + required = true + "--pullrequest", "-p" + help = "An integer that corresponds to the pull request" + required = true + arg_type = Int + "--gist", "-g" + help = "specify this argument if you want to send a gist to the pullrequest. If this option is not specified, you must specify a comment" + arg_type = String + required = false + "--comment", "-c" + help = "Comment to post on the pull request" + arg_type = String + required = true + end + + return parse_args(s, as_symbols = true) +end + +function create_gist_from_json_file(myauth) + gistfile = DEFAULT_GIST_FILE_PATH + gist = begin + open(gistfile, "r") do f + return JSON.parse(f) + end + end + posted_gist = create_gist(params = gist, auth = myauth) + return posted_gist +end + +function create_gist_from_log_file(gist_file, pullrequest_id, myauth) + file_content = "" + file = open(gist_file, "r") + + file_lines = readlines(file) + line_number = findfirst(x -> !isnothing(match(r"ERROR:", x)), file_lines) + lines = !isnothing(line_number) ? file_lines[line_number:end] : [""] + for line in lines + file_content *= line * '\n' + end + close(file) + + file_dict = Dict("$(pullrequest_id)_bmark_error.log" => Dict("content" => file_content)) + gist = Dict{String,Any}( + "description" => "Benchmark logs", + "public" => true, + "files" => file_dict, + ) + + posted_gist = GitHub.create_gist(params = gist, auth = myauth) + + return posted_gist +end + +function get_repo(api::GitHub.GitHubWebAPI, org::String, repo_name::String; kwargs...) + my_params = Dict(:visibility => "all") + # return GitHub.repo(api, repo; params = my_params, kwargs...) + return Repo( + GitHub.gh_get_json( + api, + "/repos/$(org)/$(repo_name)"; + params = my_params, + kwargs..., + ), + ) +end + +function get_pull_request( + api::GitHub.GitHubWebAPI, + org::String, + repo::Repo, + pullrequest_id; + kwargs..., +) + my_params = Dict(:sort => "popularity", :direction => "desc") + pull_request = PullRequest( + GitHub.gh_get_json( + api, + "/repos/$(org)/$(repo.name)/pulls/$(pullrequest_id)"; + params = my_params, + kwargs..., + ), + ) + return pull_request +end + +function post_comment_to_pr( + org::String, + repo_name::String, + pullrequest_id::Int, + comment::String; + kwargs..., +) + api = GitHub.DEFAULT_API + repo = get_repo(api, org, repo_name; kwargs...) + pull_request = get_pull_request(api, org, repo, pullrequest_id; kwargs...) + GitHub.create_comment(api, repo, pull_request, comment; kwargs...) +end + +function main() + # Need to add GITHUB_AUTH to your .bashrc + myauth = GitHub.authenticate(ENV["GITHUB_AUTH"]) + # parse the arguments: + parsed_args = parse_commandline() + org = parsed_args[:org] + repo_name = parsed_args[:repo] + pullrequest_id = parsed_args[:pullrequest] + gist_file = parsed_args[:gist] + comment = parsed_args[:comment] + + if !isnothing(gist_file) + if gist_file == DEFAULT_GIST_FILE_PATH + comment = "$(comment): $(create_gist_from_json_file(myauth).html_url)" + else + comment = "$(comment): $(create_gist_from_log_file(gist_file, pullrequest_id, myauth).html_url)" + end + end + post_comment_to_pr(org, repo_name, pullrequest_id, comment; auth = myauth) +end + +main() diff --git a/docs/make.jl b/docs/make.jl index fc7178a..5408c8e 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,23 +1,26 @@ using JSOTemplate using Documenter -DocMeta.setdocmeta!(JSOTemplate, :DocTestSetup, :(using JSOTemplate); recursive=true) +DocMeta.setdocmeta!(JSOTemplate, :DocTestSetup, :(using JSOTemplate); recursive = true) makedocs(; - modules=[JSOTemplate], - authors="Abel Soares Siqueira and contributors", - repo="https://github.com/JuliaSmoothOptimizers/JSOTemplate.jl/blob/{commit}{path}#{line}", - sitename="JSOTemplate.jl", - format=Documenter.HTML(; - prettyurls=get(ENV, "CI", "false") == "true", - canonical="https://JuliaSmoothOptimizers.github.io/JSOTemplate.jl", - assets=String[], - ), - pages=[ - "Home" => "index.md", - ], + modules = [JSOTemplate], + doctest = true, + linkcheck = false, + strict = false, + authors = "Abel Soares Siqueira and contributors", + repo = "https://github.com/JuliaSmoothOptimizers/JSOTemplate.jl/blob/{commit}{path}#{line}", + sitename = "JSOTemplate.jl", + format = Documenter.HTML(; + prettyurls = get(ENV, "CI", "false") == "true", + canonical = "https://JuliaSmoothOptimizers.github.io/JSOTemplate.jl", + assets = ["assets/style.css"], + ), + pages = ["Home" => "index.md", "Reference" => "reference.md"], ) deploydocs(; - repo="github.com/JuliaSmoothOptimizers/JSOTemplate.jl", + repo = "github.com/JuliaSmoothOptimizers/JSOTemplate.jl", + push_preview = true, + devbranch = "main", ) diff --git a/docs/src/assets/logo.png b/docs/src/assets/logo.png new file mode 100644 index 0000000..f1947e9 Binary files /dev/null and b/docs/src/assets/logo.png differ diff --git a/docs/src/assets/style.css b/docs/src/assets/style.css new file mode 100644 index 0000000..6fc1ce1 --- /dev/null +++ b/docs/src/assets/style.css @@ -0,0 +1,20 @@ +.mi, .mo, .mn { + color: #317293; +} + +a { + color: #3091d1; +} + +a:visited { + color: #3091d1; +} + +a:hover { + color: #ff5722; +} + +nav.toc .logo { + max-width: 256px; + max-height: 256px; +} diff --git a/docs/src/index.md b/docs/src/index.md index d594fcf..35f87d3 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -4,11 +4,4 @@ CurrentModule = JSOTemplate # JSOTemplate -Documentation for [JSOTemplate](https://github.com/JuliaSmoothOptimizers/JSOTemplate.jl). - -```@index -``` - -```@autodocs -Modules = [JSOTemplate] -``` +Documentation for [JSOTemplate](https://github.com/JuliaSmoothOptimizers/JSOTemplate.jl). \ No newline at end of file diff --git a/docs/src/reference.md b/docs/src/reference.md new file mode 100644 index 0000000..65f0e2b --- /dev/null +++ b/docs/src/reference.md @@ -0,0 +1,17 @@ +# Reference +​ +## Contents +​ +```@contents +Pages = ["reference.md"] +``` +​ +## Index +​ +```@index +Pages = ["reference.md"] +``` +​ +```@autodocs +Modules = [JSOTemplate] +``` \ No newline at end of file diff --git a/push_benchmarks.sh b/push_benchmarks.sh new file mode 100644 index 0000000..6241444 --- /dev/null +++ b/push_benchmarks.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +julia --project=benchmark -E 'using Pkg; Pkg.resolve()' +julia --project=benchmark benchmark/send_comment_to_pr.jl -o $org -r $repo -p $pullrequest -c '**Starting benchmarks!**' + +LOCAL_BRANCH_NAME="temp_bmark" +git checkout $LOCAL_BRANCH_NAME -- || true + +julia --project=benchmark benchmark/$1 $repo + +if [ "$?" -eq "0" ] ; then + julia --project=benchmark benchmark/send_comment_to_pr.jl -o $org -r $repo -p $pullrequest -c "Benchmark results" -g "gist.json" +else + ERROR_LOGS="/home/jenkins/benchmarks/$org/$repo/${pullrequest}_${BUILD_NUMBER}_bmark_error.log" + julia --project=benchmark benchmark/send_comment_to_pr.jl -o $org -r $repo -p $pullrequest -c "**An error occured while running $1**" -g $ERROR_LOGS +fi diff --git a/test/runtests.jl b/test/runtests.jl index 0bf77c5..4bb645d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,5 +2,5 @@ using JSOTemplate using Test @testset "JSOTemplate.jl" begin - # Write your tests here. + # Write your tests here. end