From 25f0a34ef212818e30ee07866877d0ea7985c691 Mon Sep 17 00:00:00 2001 From: Karl Berry Date: Tue, 18 Dec 2018 23:33:00 +0000 Subject: l3build (18dec18) git-svn-id: svn://tug.org/texlive/trunk@49450 c570f23f-e606-0410-a88d-b1316a301751 --- Master/texmf-dist/scripts/l3build/l3build-help.lua | 2 +- .../texmf-dist/scripts/l3build/l3build-stdmain.lua | 9 +- .../texmf-dist/scripts/l3build/l3build-upload.lua | 237 +++++++++++++++++++++ .../scripts/l3build/l3build-variables.lua | 4 + Master/texmf-dist/scripts/l3build/l3build.lua | 9 +- 5 files changed, 254 insertions(+), 7 deletions(-) create mode 100644 Master/texmf-dist/scripts/l3build/l3build-upload.lua (limited to 'Master/texmf-dist/scripts/l3build') diff --git a/Master/texmf-dist/scripts/l3build/l3build-help.lua b/Master/texmf-dist/scripts/l3build/l3build-help.lua index 18caf87daae..8c3877595fc 100644 --- a/Master/texmf-dist/scripts/l3build/l3build-help.lua +++ b/Master/texmf-dist/scripts/l3build/l3build-help.lua @@ -58,7 +58,7 @@ function help() end print("usage: " .. scriptname .. " [] []") print("") - print("The most commonly used l3build commands are:") + print("Valid commands are:") local longest,t = setup_list(target_list) for _,k in ipairs(t) do local target = target_list[k] diff --git a/Master/texmf-dist/scripts/l3build/l3build-stdmain.lua b/Master/texmf-dist/scripts/l3build/l3build-stdmain.lua index 26a229ce31c..f2d8797c830 100644 --- a/Master/texmf-dist/scripts/l3build/l3build-stdmain.lua +++ b/Master/texmf-dist/scripts/l3build/l3build-stdmain.lua @@ -58,7 +58,7 @@ target_list = return 0 end }, - bundlectan = + bundlectan = { func = bundlectan }, @@ -138,7 +138,12 @@ target_list = bundle_target = true, desc = "Unpacks the source files into the build tree", func = unpack - } + }, + upload = + { + desc = "Send archive to CTAN for public release", + func = upload + }, } -- diff --git a/Master/texmf-dist/scripts/l3build/l3build-upload.lua b/Master/texmf-dist/scripts/l3build/l3build-upload.lua new file mode 100644 index 00000000000..68692b0af4c --- /dev/null +++ b/Master/texmf-dist/scripts/l3build/l3build-upload.lua @@ -0,0 +1,237 @@ +--[[ + +File l3build-upload.lua Copyright (C) 2018 The LaTeX3 Project + +It may be distributed and/or modified under the conditions of the +LaTeX Project Public License (LPPL), either version 1.3c of this +license or (at your option) any later version. The latest version +of this license is in the file + + http://www.latex-project.org/lppl.txt + +This file is part of the "l3build bundle" (The Work in LPPL) +and all files in that bundle must be distributed together. + +----------------------------------------------------------------------- + +The development version of the bundle can be found at + + https://github.com/latex3/l3build + +for those people who are interested. + +--]] + + +-- UPLOAD() +-- +-- takes a package configuration table and an optional boolean +-- +-- if the upload parameter is not supplied or is not true, only package validation +-- is used, if upload is true then package upload will be attempted if validation +-- succeeds. + +-- fields are given as a string, or optionally for fields allowing multiple +-- values, as a table of strings. + +-- Mandatory fields are checked in Lua +-- Maximum string lengths are checked. + +-- Currently string values are not checked, eg licence names, or URL syntax. + +-- The input form could be used to construct a post body but +-- luasec is not included in texlua. Instead an external program is used to post. +-- As Windows (since April 2018) includes curl now use curl. +-- A version using ctan-o-mat is available in the ctan-post github repo. + +-- the main interface is +-- upload() +-- with a configuration table `uploadconfig` + + +local curl_debug = curl_debug or false -- to disable posting +-- For now, this is undocumented. + +local ctanupload = ctanupload or "ask" +-- if ctanupload is nil or false, only validation is attempted +-- if ctanupload is true the ctan upload URL will be used after validation +-- if upload is anything else, the user will be prompted whether to upload. +-- For now, this is undocumented. I think I would prefer to keep it always set to ask for the time being. + +function upload() + + local uploadfile = ctanzip..".zip" + + -- try a sensible default for the package name: + uploadconfig.pkg = uploadconfig.pkg or ctanpkg or nil + + -- start building the curl command: + ctan_post = curlexe .. " " + + -- build up the curl command field-by-field: + + -- field max desc mandatory multi + -- ---------------------------------------------------------------------------------------------------- + ctan_field("announcement", uploadconfig.announcement, 8192, "Announcement", false, false ) + ctan_field("author", uploadconfig.author, 128, "Author name", true, false ) + ctan_field("bugtracker", uploadconfig.bugtracker, 255, "URL(s) of bug tracker", false, true ) + ctan_field("ctanPath", uploadconfig.ctanPath, 255, "CTAN path", true, false ) + ctan_field("description", uploadconfig.description, 4096, "Short description of package", false, false ) + ctan_field("development", uploadconfig.development, 255, "URL(s) of development channels", false, true ) + ctan_field("email", uploadconfig.email, 255, "Email of uploader", true, false ) + ctan_field("home", uploadconfig.home, 255, "URL(s) of home page", false, true ) + ctan_field("license", uploadconfig.license, 2048, "Package license(s)", true, true ) + ctan_field("note", uploadconfig.note, 4096, "Internal note to ctan", false, false ) + ctan_field("pkg", uploadconfig.pkg, 32, "Package name", true, false ) + ctan_field("repository", uploadconfig.repository, 255, "URL(s) of source repositories", false, true ) + ctan_field("summary", uploadconfig.summary, 128, "One-line summary of package", true, false ) + ctan_field("support", uploadconfig.support, 255, "URL(s) of support channels", false, true ) + ctan_field("topic", uploadconfig.topic, 1024, "Topic(s)", false, true ) + ctan_field("update", uploadconfig.update, 8, "Boolean: true=update, false=new pkg", false, false ) + ctan_field("uploader", uploadconfig.uploader, 255, "Name of uploader", true, false ) + ctan_field("version", uploadconfig.version, 32, "Package version", true, false ) + + -- finish constructing the curl command: + ctan_post = ctan_post .. " --form 'file=@" .. tostring(uploadfile) .. ";filename=" .. tostring(uploadfile) .. "'" + ctan_post = ctan_post .. " https://ctan.org/submit/" + + -- avoid lower level error from post command if zip file missing + local zip=io.open(trim_space(tostring(uploadfile)),"r") + if zip~=nil then + io.close(zip) + else + error("Missing zip file '" .. tostring(uploadfile) .. "'") + end + + -- call post command to validate the upload at CTAN's validate URL + local exit_status=0 + local fp_return="" + + -- use popen not execute so get the return body local exit_status=os.execute(ctan_post .. "validate") + if (curl_debug==false) then + local fp = assert(io.popen(ctan_post .. "validate", 'r')) + fp_return = assert(fp:read('*a')) + fp:close() + else + fp_return="WARNING: curl_debug==true: posting disabled" + print(ctan_post) + end + if string.match(fp_return,"WARNING") or string.match(fp_return,"ERROR") then + exit_status=1 + end + + -- if upload requested and validation succeeded repost to the upload URL + if (exit_status==0 or exit_status==nil) then + if (ctanupload ~=nil and ctanupload ~=false and ctanupload ~= true) then + print("Validation successful, do you want to upload to CTAN? [y/n]" ) + local answer="" + io.write("> ") + io.flush() + answer=io.read() + if(string.lower(answer,1,1)=="y") then + ctanupload=true + end + end + if (ctanupload==true) then + local fp = assert(io.popen(ctan_post .. "upload", 'r')) + fp_return = assert(fp:read('*a')) + fp:close() +-- this is just html, could save to a file +-- or echo a cleaned up version + print('Response from CTAN:') + print(fp_return) + if string.match(fp_return,"WARNING") or string.match(fp_return,"ERROR") then + exit_status=1 + end + else + print("CTAN validation successful") + end + else + error("Warnings from CTAN package validation:\n" .. fp_return) + end + return exit_status +end + + +function trim_space(s) + return (s:gsub("^%s*(.-)%s*$", "%1")) +end + + +function ctan_field(fname,fvalue,max,desc,mandatory,multi) + if (type(fvalue)=="table" and multi==true) then + for i, v in pairs(fvalue) do + ctan_single_field(fname,v,max,desc,mandatory and i==1) + end + else + ctan_single_field(fname,fvalue,max,desc,mandatory) + end +end + + +function ctan_single_field(fname,fvalue,max,desc,mandatory) + print('ctan-post: ' .. fname .. ' ' ..tostring(fvalue or '??')) + if ((fvalue==nil and mandatory) or (fvalue == 'ask')) then + if (max < 256) then + fvalue=input_single_line_field(fname) + else + fvalue=input_multi_line_field(fname) + end + end + if (fvalue==nil or type(fvalue)~="table") then + local vs=trim_space(tostring(fvalue)) + if (mandatory==true and (fvalue == nil or vs=="")) then + error("The field " .. fname .. " must contain " .. desc) + end + if (fvalue ~=nil and string.len(vs) > 0) then + if (max > 0 and string.len(vs) > max) then + error("The field " .. fname .. " is longer than " .. max) + end + vs = vs:gsub('"','\\"') + vs = vs:gsub('`','\\`') + ctan_post=ctan_post .." --form " .. fname .. '="' .. vs .. '"' + end + else + error("The value of the field '" .. fname .."' must be a scalar not a table") + end +end + + +-- function for interactive multiline fields +function input_multi_line_field (name) + print("Enter " .. name .. " three or ctrl-D to stop") + + local field="" + + local answer_line + local return_count=0 + repeat + io.write("> ") + io.flush() + answer_line=io.read() + if answer_line=="" then + return_count=return_count+1 + else + for i=1,return_count,1 do + field = field .. "\n" + end + return_count=0 + if answer_line~=nil then + field = field .. "\n" .. answer_line + end + end + until (return_count==3 or answer_line==nil) + return field +end + +function input_single_line_field(name) + print("Enter " .. name ) + + local field="" + + io.write("> ") + io.flush() + field=io.read() + return field +end + diff --git a/Master/texmf-dist/scripts/l3build/l3build-variables.lua b/Master/texmf-dist/scripts/l3build/l3build-variables.lua index b3b999755dd..ad88aed00ce 100644 --- a/Master/texmf-dist/scripts/l3build/l3build-variables.lua +++ b/Master/texmf-dist/scripts/l3build/l3build-variables.lua @@ -185,3 +185,7 @@ tpfext = tpfext or ".tpf" -- Manifest options manifestfile = manifestfile or "MANIFEST.md" + +-- Upload settings +curlexe = curlexe or "curl" +uploadconfig = uploadconfig or {} diff --git a/Master/texmf-dist/scripts/l3build/l3build.lua b/Master/texmf-dist/scripts/l3build/l3build.lua index e40b8e6da74..3422f4d81a9 100755 --- a/Master/texmf-dist/scripts/l3build/l3build.lua +++ b/Master/texmf-dist/scripts/l3build/l3build.lua @@ -1,4 +1,4 @@ -#!/usr/bin/env texlua +#!/usr/bin/env texlua --[[ @@ -65,6 +65,7 @@ build_require("unpack") build_require("manifest") build_require("manifest-setup") build_require("tagging") +build_require("upload") build_require("stdmain") -- This has to come after stdmain(), @@ -96,7 +97,7 @@ end build_require("variables") -- Ensure that directories are 'space safe' -maindir = escapepath(maindir) +maindir = escapepath(maindir) docfiledir = escapepath(docfiledir) sourcefiledir = escapepath(sourcefiledir) supportdir = escapepath(supportdir) @@ -125,12 +126,12 @@ check_engines() -- -- Deal with multiple configs for tests -- - + -- When we have specific files to deal with, only use explicit configs -- (or just the std one) if options["names"] then checkconfigs = options["config"] or {stdconfig} -else +else checkconfigs = options["config"] or checkconfigs end -- cgit v1.2.3