From 20eefefc0d8ae574d0114e168d9b7116160d2bc6 Mon Sep 17 00:00:00 2001 From: Wei Li Date: Thu, 9 Jul 2026 14:17:27 -0400 Subject: [PATCH 1/3] Route compiled-code output to the R console (Bioconductor) Bioconductor / R CMD check flags compiled code that writes to stdout/stderr. Replace std::cout/std::cerr/printf with Rcpp::Rcout/Rcpp::Rcerr/Rprintf in src/RRA.cpp, src/fileio.cpp and src/rngs.cpp (fprintf() calls that write to the RRA output *file* are unchanged). Verified: scMAGeCK.so no longer references std::cout/cerr/printf/puts/putchar, and scmageck_rra output on the bundled demo is byte-identical to before (full result table all.equal == TRUE; lo_value sums unchanged). --- src/RRA.cpp | 97 +++++++++++++++++++++++++------------------------- src/fileio.cpp | 23 ++++++------ src/rngs.cpp | 9 ++--- 3 files changed, 66 insertions(+), 63 deletions(-) diff --git a/src/RRA.cpp b/src/RRA.cpp index 64376cc..01c165f 100644 --- a/src/RRA.cpp +++ b/src/RRA.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include using namespace std; @@ -100,7 +101,7 @@ int loadControlSeq(const char* fname){ ifstream fh; fh.open(fname); if(!fh.is_open()){ - cerr<<"Error opening "< & argv) { return 0; } - printf("Welcome to RRA v %s.\n", RRA_VERSION); + Rprintf("Welcome to RRA v %s.\n", RRA_VERSION); inputFileName[0] = 0; outputFileName[0] = 0; maxPercentile = 0.1; @@ -182,7 +183,7 @@ int RRA_main (vector & argv) { } if (argv[i-1] == "--skip-gene"){ string sn(argv[i]); - cout<<"Skipping gene "< & argv) { if ((inputFileName[0]==0)||(outputFileName[0]==0)) { - cerr<<"Error: input file or output file name not set.\n"; + Rcpp::Rcerr<<"Error: input file or output file name not set.\n"; PrintCommandUsage(argv[0].c_str()); return -1; } if ((maxPercentile>1.0)||(maxPercentile<0.0)) { - cerr<<("Error: maxPercentile should be within 0.0 and 1.0\n"); + Rcpp::Rcerr<<("Error: maxPercentile should be within 0.0 and 1.0\n"); return -1; } if ((minPercentile>1.0)) { - cerr<<("Error: minPercentile should be less than 1.0\n"); + Rcpp::Rcerr<<("Error: minPercentile should be less than 1.0\n"); return -1; } @@ -225,28 +226,28 @@ int RRA_main (vector & argv) { assert(groups!=NULL); assert(lists!=NULL); - printf("Reading input file...\n"); + Rprintf("Reading input file...\n"); flag = ReadFile(inputFileName, groups, MAX_GROUP_NUM, &groupNum, lists, MAX_LIST_NUM, &listNum); if (flag<=0){ - cerr<<"\nError: reading ranking file ...\n"; + Rcpp::Rcerr<<"\nError: reading ranking file ...\n"; return -1; } - cerr<<("Computing lo-values for each group...\n"); + Rcpp::Rcerr<<("Computing lo-values for each group...\n"); if (ProcessGroups(groups, groupNum, lists, listNum, minPercentile, maxPercentile)<=0) { - cerr<<("\nError: processing groups failed.\n"); + Rcpp::Rcerr<<("\nError: processing groups failed.\n"); return -1; } - cerr<<("Computing false discovery rate...\n"); + Rcpp::Rcerr<<("Computing false discovery rate...\n"); if(rand_passnum*groupNum<100000 && has_specified_rand_passnum_parameter==false){ rand_passnum=100000/groupNum+1; - cout<<"Increase the number of permutations to "< & argv) { } if (i<=0) { - cerr<<("\nError: computing FDR failed.\n"); + Rcpp::Rcerr<<("\nError: computing FDR failed.\n"); return -1; } adjustFDR(groups,groupNum); - cerr<<("Saving to output file...\n"); + Rcpp::Rcerr<<("Saving to output file...\n"); if (SaveGroupInfo(outputFileName, groups, groupNum)<=0) { - cerr<<("\nError: saving output file failed.\n"); + Rcpp::Rcerr<<("\nError: saving output file failed.\n"); return -1; } - cerr<<("RRA completed.\n"); + Rcpp::Rcerr<<("RRA completed.\n"); for(i=0;i & argv) { } free(lists); - cerr<<("Lists deletion complete.\n"); + Rcpp::Rcerr<<("Lists deletion complete.\n"); if(UseControlSeq){ - cerr<<("ControlSeqPercentile deletion complete.\n"); + Rcpp::Rcerr<<("ControlSeqPercentile deletion complete.\n"); delete[] ControlSeqPercentile; } @@ -298,21 +299,21 @@ int RRA_main (vector & argv) { void PrintCommandUsage(const char *command) { //print the options of the command - printf("%s - Robust Rank Aggreation v %s.\n", command, RRA_VERSION); - printf("usage:\n"); - printf("-i . Format: [] []\n"); - printf("-o . Format: \n"); - printf("-p . RRA only consider the items with percentile smaller than this parameter. Default=0.1\n"); - printf("-P . RRA only consider the items with percentile greater than this parameter. Default=-1.0\n"); - printf("--control . A list of control sgRNA names.\n"); - printf("--permutation . The number of rounds of permutation. Increase this value if the number of genes is small. Default 100.\n"); - printf("--no-permutation-by-group. By default, gene permutation is performed separately, by their number of sgRNAs. Turning this option will perform permutation on all genes together. This makes the program faster, but the p value estimation is accurate only if the number of sgRNAs per gene is approximately the same.\n"); - printf("--skip-gene . Genes to skip from doing permutation. Specify it multiple times if you need to skip more than 1 genes.\n"); - printf("--min-percentage-goodsgrna . Filter genes that have too few percentage of 'good sgrnas', or sgrnas that fall below the -p threshold. Must be a number between 0-1. Default 0 (do not filter genes).\n"); - printf("--min-number-goodsgrna . Filter genes that have too few number of 'good sgrnas', or sgrnas that fall below the -p threshold. Must be an integer. Default 0 (do not filter genes). \n"); - printf("--max-sgrnapergene-permutation . Only permute genes by group if the number of sgRNAs per gene is smaller than this number. This will save a lot of time if some regions are targeted by a large number of sgRNAs (usually hundreds). Must be an integer. Default 100. \n"); - printf("example:\n"); - printf("%s -i input.txt -o output.txt -p 0.1 \n", command); + Rprintf("%s - Robust Rank Aggreation v %s.\n", command, RRA_VERSION); + Rprintf("usage:\n"); + Rprintf("-i . Format: [] []\n"); + Rprintf("-o . Format: \n"); + Rprintf("-p . RRA only consider the items with percentile smaller than this parameter. Default=0.1\n"); + Rprintf("-P . RRA only consider the items with percentile greater than this parameter. Default=-1.0\n"); + Rprintf("--control . A list of control sgRNA names.\n"); + Rprintf("--permutation . The number of rounds of permutation. Increase this value if the number of genes is small. Default 100.\n"); + Rprintf("--no-permutation-by-group. By default, gene permutation is performed separately, by their number of sgRNAs. Turning this option will perform permutation on all genes together. This makes the program faster, but the p value estimation is accurate only if the number of sgRNAs per gene is approximately the same.\n"); + Rprintf("--skip-gene . Genes to skip from doing permutation. Specify it multiple times if you need to skip more than 1 genes.\n"); + Rprintf("--min-percentage-goodsgrna . Filter genes that have too few percentage of 'good sgrnas', or sgrnas that fall below the -p threshold. Must be a number between 0-1. Default 0 (do not filter genes).\n"); + Rprintf("--min-number-goodsgrna . Filter genes that have too few number of 'good sgrnas', or sgrnas that fall below the -p threshold. Must be an integer. Default 0 (do not filter genes). \n"); + Rprintf("--max-sgrnapergene-permutation . Only permute genes by group if the number of sgRNAs per gene is smaller than this number. This will save a lot of time if some regions are targeted by a large number of sgRNAs (usually hundreds). Must be an integer. Default 100. \n"); + Rprintf("example:\n"); + Rprintf("%s -i input.txt -o output.txt -p 0.1 \n", command); } @@ -381,7 +382,7 @@ int ProcessGroups(GROUP_STRUCT *groups, int groupNum, LIST_STRUCT *lists, int li if(validsgs<=1){ isallone=true; } - //printf("Gene: %s\n",groups[i].name); + //Rprintf("Gene: %s\n",groups[i].name); if(isallone){ ComputeLoValue(tmpF, validsgs, groups[i].loValue, minPercentile,maxPercentile, groups[i].goodsgrnas); }else{ @@ -392,7 +393,7 @@ int ProcessGroups(GROUP_STRUCT *groups, int groupNum, LIST_STRUCT *lists, int li if(groups[i].controlsgs==groups[i].itemNum){ string sn(groups[i].name); gene_to_skip[sn]=1; - cout<<"Skipping gene "<::iterator mit = ControlSeqMap.begin(); mit != ControlSeqMap.end(); mit++){ if(ControlSeqPercentile[mit->second]<0){ - cerr<<"Warning: sgRNA "<first<<" not found in the ranked list. \n"; + Rcpp::Rcerr<<"Warning: sgRNA "<first<<" not found in the ranked list. \n"; //ControlSeqPercentile[mit->second]=0.5; } } @@ -526,12 +527,12 @@ int ComputeLoValue_Prob(double *percentiles, //array of percentiles } - if(PRINT_DEBUG) printf("probs:"); + if(PRINT_DEBUG) Rprintf("probs:"); for(i=0;i pvalue_in; for(i=0;i::iterator mii=itemNumMap.begin();mii!=itemNumMap.end();mii++){ int sgrnanum=mii->first; if(sgrnanum<=max_sgnum_permutation_by_group){// to avoid permutations on genes with too many sgRNAs (take too much time) randLoValueNum = 0; - cout<<"Permuting genes with "< #include #include +#include using namespace std; #include "fileio.h" @@ -36,7 +37,7 @@ int getGroupListNum(char* fileName, GROUP_STRUCT* &groups, LIST_STRUCT* lists, i ifstream fh; fh.open(fileName); if(!fh.is_open()){ - cerr<<"Error opening "< 6){ - cerr<<"Error: incorrect input file format: [] [iscounted]\n"; + Rcpp::Rcerr<<"Error: incorrect input file format: [] [iscounted]\n"; fh.close(); return -1; } @@ -84,7 +85,7 @@ int getGroupListNum(char* fileName, GROUP_STRUCT* &groups, LIST_STRUCT* lists, i groupNames[subwstr]=tmpGroupNum; tmpGroupNum ++; if (tmpGroupNum >= maxGroupNum){ - printf("Warning: too many groups; maxGroupNum = %d\n. Will try to double the maximum number of groups..", maxGroupNum); + Rprintf("Warning: too many groups; maxGroupNum = %d\n. Will try to double the maximum number of groups..", maxGroupNum); GROUP_STRUCT* gp2=new GROUP_STRUCT[maxGroupNum*2]; memcpy(gp2,groups,maxGroupNum*sizeof(GROUP_STRUCT)); delete []groups; @@ -108,7 +109,7 @@ int getGroupListNum(char* fileName, GROUP_STRUCT* &groups, LIST_STRUCT* lists, i listNames[thisliststr]=tmpListNum; tmpListNum ++; if (tmpListNum >= maxListNum){ - printf("Error: too many lists. maxListNum = %d\n", maxListNum); + Rprintf("Error: too many lists. maxListNum = %d\n", maxListNum); return -1; } } @@ -162,14 +163,14 @@ int ReadFile(char *fileName, GROUP_STRUCT* &groups, int maxGroupNum, int *groupN } // construct the structure for (i=0;i=groups[i].itemNum){ //skip those that consists of only control sgrnas - printf("Suppressing the output of gene %s since it is negative ontrol genes.\n",groups[i].name); + Rprintf("Suppressing the output of gene %s since it is negative ontrol genes.\n",groups[i].name); continue; } if(groups[i].isbad==0){ diff --git a/src/rngs.cpp b/src/rngs.cpp index a55f176..7387d27 100644 --- a/src/rngs.cpp +++ b/src/rngs.cpp @@ -33,6 +33,7 @@ */ #include +#include #include #include "rngs.h" @@ -116,11 +117,11 @@ static int initialized = 0; /* test for stream initialization */ x = ((unsigned long) time((time_t *) NULL)) % MODULUS; if (x == 0) while (!ok) { - printf("\nEnter a positive integer seed (9 digits or less) >> "); + Rprintf("\nEnter a positive integer seed (9 digits or less) >> "); scanf("%ld", &x); ok = (0 < x) && (x < MODULUS); if (!ok) - printf("\nInput out of range ... try again\n"); + Rprintf("\nInput out of range ... try again\n"); } seed[stream] = x; } @@ -173,7 +174,7 @@ static int initialized = 0; /* test for stream initialization */ GetSeed(&x); /* get the state of stream 1 */ ok = ok && (x == A256); /* x should be the jump multiplier */ if (ok) - printf("\n The implementation of rngs.c is correct.\n\n"); + Rprintf("\n The implementation of rngs.c is correct.\n\n"); else - printf("\n\a ERROR -- the implementation of rngs.c is not correct.\n\n"); + Rprintf("\n\a ERROR -- the implementation of rngs.c is not correct.\n\n"); } From 8d5365b40d5a556e6f5d231bf3048b9b73dd97af Mon Sep 17 00:00:00 2001 From: Wei Li Date: Thu, 9 Jul 2026 14:17:27 -0400 Subject: [PATCH 2/3] Set Wei Li as package maintainer Switch the Authors@R cre role to Wei Li for the Bioconductor resubmission (Xiaolong Cheng and Lin Yang remain authors). Note the compiled-code fix in NEWS. --- DESCRIPTION | 4 ++-- NEWS.md | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 8606582..cc06c0b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -4,8 +4,8 @@ Title: Identify genes associated with multiple expression phenotypes in single-c Version: 0.99.0 Date: 2026-07-09 Authors@R: c( - person("Wei", "Li", role = "aut"), - person("Xiaolong", "Cheng", email = "xiaolongcheng1120@gmail.com", role = c("aut", "cre")), + person("Wei", "Li", email = "li.david.wei@gmail.com", role = c("aut", "cre")), + person("Xiaolong", "Cheng", email = "xiaolongcheng1120@gmail.com", role = "aut"), person("Lin", "Yang", role = "aut")) Description: scMAGeCK is a computational model to identify genes associated with multiple expression phenotypes from CRISPR screening coupled with single-cell RNA sequencing data (CROP-seq). License: BSD_2_clause + file LICENSE diff --git a/NEWS.md b/NEWS.md index b9fe974..8f8cdd4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -8,4 +8,7 @@ `FetchData()` with `layer=` (defunct in SeuratObject >= 5.4). - The bundled example object is now stored in the Seurat v5 format. * `DESCRIPTION` now requires `Seurat (>= 5.0.0)`. +* Compiled code (RRA) now writes diagnostics to the R console via + `Rcpp::Rcout`/`Rcpp::Rcerr`/`Rprintf` instead of `std::cout`/`printf`. +* R CMD check / BiocCheck clean-up (dead code, namespace, docs, examples). * Version reset to 0.99.0 for Bioconductor resubmission. From 695e4211079a39c044882a3168ad884898e26fb1 Mon Sep 17 00:00:00 2001 From: Wei Li Date: Thu, 9 Jul 2026 15:24:38 -0400 Subject: [PATCH 3/3] Address Copilot review: harden C++ file I/O and remove stdin read - loadControlSeq / getGroupListNum / ReadFile: return early on file-open failure instead of continuing on a failed stream. - loadControlSeq: guard the trailing-char access so empty lines are not read out of bounds (undefined behavior). - PutSeed(x == 0): seed from the clock like x < 0 instead of prompting via scanf() -- reading stdin can hang non-interactive R runs. - Fix two user-facing message typos ("Aggreation", "ontrol"). RRA output on the bundled demo remains byte-identical (all.equal == TRUE; lo_value sums and control-sequence count unchanged). --- src/RRA.cpp | 11 +++++++---- src/fileio.cpp | 8 +++++--- src/rngs.cpp | 20 ++++++-------------- 3 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/RRA.cpp b/src/RRA.cpp index 01c165f..697db5c 100644 --- a/src/RRA.cpp +++ b/src/RRA.cpp @@ -102,15 +102,18 @@ int loadControlSeq(const char* fname){ fh.open(fname); if(!fh.is_open()){ Rcpp::Rcerr<<"Error opening "< & argv) { void PrintCommandUsage(const char *command) { //print the options of the command - Rprintf("%s - Robust Rank Aggreation v %s.\n", command, RRA_VERSION); + Rprintf("%s - Robust Rank Aggregation v %s.\n", command, RRA_VERSION); Rprintf("usage:\n"); Rprintf("-i . Format: [] []\n"); Rprintf("-o . Format: \n"); diff --git a/src/fileio.cpp b/src/fileio.cpp index 0ec1bf3..8303eee 100644 --- a/src/fileio.cpp +++ b/src/fileio.cpp @@ -38,10 +38,11 @@ int getGroupListNum(char* fileName, GROUP_STRUCT* &groups, LIST_STRUCT* lists, i fh.open(fileName); if(!fh.is_open()){ Rcpp::Rcerr<<"Error opening "< vwords; vector vsubwords; wordNum=stringSplit(oneline," \t\r\n\v",vwords); @@ -180,8 +181,9 @@ int ReadFile(char *fileName, GROUP_STRUCT* &groups, int maxGroupNum, int *groupN fh.open(fileName); if(!fh.is_open()){ Rcpp::Rcerr<<"Error opening "<=groups[i].itemNum){ //skip those that consists of only control sgrnas - Rprintf("Suppressing the output of gene %s since it is negative ontrol genes.\n",groups[i].name); + Rprintf("Suppressing the output of gene %s since it consists only of negative control sgRNAs.\n",groups[i].name); continue; } if(groups[i].isbad==0){ diff --git a/src/rngs.cpp b/src/rngs.cpp index 7387d27..d573942 100644 --- a/src/rngs.cpp +++ b/src/rngs.cpp @@ -104,25 +104,17 @@ static int initialized = 0; /* test for stream initialization */ * Use this function to set the state of the current random number * generator stream according to the following conventions: * if x > 0 then x is the state (unless too large) - * if x < 0 then the state is obtained from the system clock - * if x = 0 then the state is to be supplied interactively + * if x <= 0 then the state is obtained from the system clock * --------------------------------------------------------------- + * Note: reading a seed interactively from stdin is not safe inside an + * R package (it would hang non-interactive/batch runs), so x == 0 is + * treated the same as x < 0 (seed from the clock). */ { - char ok = 0; - if (x > 0) x = x % MODULUS; /* correct if x is too large */ - if (x < 0) - x = ((unsigned long) time((time_t *) NULL)) % MODULUS; - if (x == 0) - while (!ok) { - Rprintf("\nEnter a positive integer seed (9 digits or less) >> "); - scanf("%ld", &x); - ok = (0 < x) && (x < MODULUS); - if (!ok) - Rprintf("\nInput out of range ... try again\n"); - } + if (x <= 0) + x = ((unsigned long) time((time_t *) NULL)) % MODULUS; seed[stream] = x; }